Skip to content

feat(checks): add proxy protocol fingerprint check (#52) - #60

Open
MsfPablo wants to merge 1 commit into
francomano:masterfrom
MsfPablo:feat/52-proxy-fingerprint
Open

feat(checks): add proxy protocol fingerprint check (#52)#60
MsfPablo wants to merge 1 commit into
francomano:masterfrom
MsfPablo:feat/52-proxy-fingerprint

Conversation

@MsfPablo

@MsfPablo MsfPablo commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Implements #52.

What

Adds a new protocol-category check, proxy_fingerprint, that auto-detects which proxy protocol a server actually speaks and validates it against the configured --proxy-type.

How

The check opens one fresh TCP connection per protocol and sends a minimal greeting, classifying the peer by how it replies:

Probe Greeting Match signal
SOCKS5 no-auth method negotiation 05 01 00 reply version byte 0x05
SOCKS4 CONNECT request for a fixed target reply version byte 0x00 (distinct from the 0x04 request version)
HTTP CONNECT CONNECT 1.1.1.1:53 HTTP/1.1 reply begins with HTTP/

Each probe is independent so a misbehaving peer cannot starve the others; a 5s per-handshake deadline bounds the whole check.

evaluateFingerprint is a pure, testable helper that decides the verdict:

  • auto / unsetpassed, reports the detected protocol and suggests pinning --proxy-type.
  • declared matches detectedpassed.
  • https-declared, http-detectedpassed (both are HTTP CONNECT at the application layer; the plaintext probe cannot distinguish TLS-to-proxy).
  • declared ≠ detectedfailed with a concrete --proxy-type <detected> suggestion.
  • no greeting answerederror (endpoint offline, expects TLS, or unsupported protocol).
  • multiple greetings answeredfailed, reports the multiprotocol result.

Direct connections are skipped, consistent with the other proxy-only checks.

Files

  • core/checks/proxy_fingerprint/check.go — the check, probes, and pure verdict helper.
  • core/checks/proxy_fingerprint/check_test.go — hermetic net.Listener mock per protocol dialect; no external network.
  • core/checks/register.go — registered in RegisterDefaults.
  • README.md — row added to the built-in checks table.

Tests

go test ./core/checks/proxy_fingerprint/ -count=1
ok  github.com/francomano/proxydoctor/core/checks/proxy_fingerprint  0.235s

14 tests: detection of each protocol, declared-matches, https/http reconciliation, mismatch-with-suggestion, auto-detection, unreachable-endpoint error, skip-direct, and the pure verdict helper paths.

Add a protocol-category check that auto-detects which proxy protocol a
server actually speaks by probing it with SOCKS5, SOCKS4 and HTTP CONNECT
greetings on separate connections, then validates the result against the
user-configured --proxy-type.

- SOCKS5 probe sends a no-auth method negotiation (05 01 00) and matches
  on a 0x05 version reply.
- SOCKS4 probe sends a CONNECT for a fixed target and matches on the
  0x00 reply version byte (distinct from the 0x04 request version).
- HTTP CONNECT probe matches on an "HTTP/" reply prefix.
- evaluateFingerprint is a pure helper: it reports the detected protocol,
  passes when declared matches (with https satisfying http detection,
  since both are HTTP CONNECT at the application layer), fails with a
  concrete --proxy-type suggestion on mismatch, errors when no greeting
  is answered, and handles auto-detection.

Registered in core/checks/register.go and added to the README checks
table. Tests use a hermetic net.Listener mock that replies per protocol
dialect; no external network is required.

@francomano francomano left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Richiesta di modifica: probeTimeout viene passato solo a net.DialTimeout; dopo la connessione, io.ReadFull e conn.Read non hanno una deadline. Un endpoint che accetta la TCP connection e non risponde può bloccare il check oltre 5 secondi (e oltre il timeout della diagnosi), in contrasto con la descrizione della PR. Imposta una read/write deadline per ogni connessione (e verifica con un test di peer silenzioso).

@francomano francomano left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English version of my change request: probeTimeout is passed only to net.DialTimeout; after a connection is established, io.ReadFull and conn.Read have no deadline. An endpoint that accepts the TCP connection but never responds can block the check for more than five seconds (and beyond the diagnosis timeout), contrary to the PR description. Please set read/write deadlines on every connection and cover this with a silent-peer test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants